package bsh.dyn;

import java.math.BigDecimal;

import org.objectweb.asm.MethodVisitor;

import bsh.CompilationState;
import bsh.CompiledScript;
import bsh.Primitive;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * {@link CompilableTransformation} that unboxes a primitive wrapper
 * 
 * @author takeshi
 */
public class UnboxTransformation implements CompilableTransformation {

    private static final int DEFAULT_WEIGHT = 1000;

    private final ActualParameter actualParameter;

    /**
     * C'tor
     * 
     * @param actualParameter the {@link ActualParameter}. Must be a primitive wrapper
     */
    public UnboxTransformation(ActualParameter actualParameter) {
        if (!actualParameter.isPrimitiveWrapper()) {
            throw new IllegalArgumentException();
        }
        this.actualParameter = actualParameter;
    }

    /**
     * {@inheritDoc}
     */
    public int getWeight() {
        return DEFAULT_WEIGHT;
    }

    /**
     * {@inheritDoc}
     */
    public ActualParameter getTransformationTarget() {
        if (BigDecimal.class.equals(this.actualParameter.getJavaType())) {
            return this.actualParameter;
        }
        Class unboxType = Primitive.unboxType(this.actualParameter.getJavaType());
        return new PrimitiveParameter(unboxType);
    }

    /**
     * {@inheritDoc}
     */
    public boolean acceptsType(ActualParameter parameter) {
        return parameter.equals(this.actualParameter);
    }

    /**
     * {@inheritDoc}
     */
    public Object transform(Object object, CompiledScript script) {
        return object;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isCompilable() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
        state.assertStackTopType(JavaClassWrapper.wrapperFor(actualParameter.getJavaType()));
        state.popStack();
        Class targetType = Primitive.unboxType(this.actualParameter.getJavaType());
        BytecodeHelper.autoUnbox(visitor, targetType);
        state.pushStack(targetType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.actualParameter == null) ? 0 : this.actualParameter.hashCode());
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        UnboxTransformation other = (UnboxTransformation) obj;
        if (this.actualParameter == null) {
            if (other.actualParameter != null) {
                return false;
            }
        } else if (!this.actualParameter.equals(other.actualParameter)) {
            return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "UnboxTransformation [from: " + this.actualParameter.getJavaType() + " to: "
                + Primitive.unboxType(this.actualParameter.getJavaType()) + "]";
    }

}
